package com.abeir.bilibili.convert;

import com.abeir.bilibili.utils.ProgramExecutor;

import java.io.*;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;

public class FfmpegMerger implements Merger<RandomAccessFile, RandomAccessFile, String, File> {

    private final Builder options;

    private FfmpegMerger(Builder options){
        this.options = options;
    }

    public static Builder newBuilder(){
        return new Builder();
    }

    @Override
    public Optional<File> merge(RandomAccessFile video, RandomAccessFile audio, String title) throws Exception {
        Path tempDirectory = options.tempDirectory;

        Path tempVideo = Path.of(tempDirectory.toString(), title + ".v.tmp");
        Path tempAudio = Path.of(tempDirectory.toString(), title + ".a.tmp");

        write(video, tempVideo);
        write(audio, tempAudio);

        String outfile = execFfmpeg(tempVideo, tempAudio, title);
        if(outfile == null){
            return Optional.empty();
        }
        return Optional.of(new File(outfile));
    }

    private void write(RandomAccessFile in, Path out) throws IOException {
        Files.deleteIfExists(out);
        try(FileOutputStream output = new FileOutputStream(out.toFile())){
            byte[] buffer = new byte[4096];
            int len;
            while((len = in.read(buffer)) != -1){
                output.write(buffer, 0, len);
            }
        }
    }

    private String execFfmpeg(Path video, Path audio, String title) throws IOException, InterruptedException {
        String outfile = Path.of(options.outDirectory.toString(), title + ".mp4").toString();
        ProgramExecutor executor = new ProgramExecutor();
        executor.setWorkingDirectory(options.outDirectory);
        executor.setEnableOutputConsole(options.enableOutputConsole);
        int code = executor.exec(options.ffmpeg.toString(), "-i", video.toString(), "-i", audio.toString(), "-codec", "copy", outfile);
        if(code == 0){
            return outfile;
        }
        return null;
    }


    public static final class Builder {
        private Path ffmpeg;
        private Path tempDirectory;
        private Path outDirectory;
        private boolean enableOutputConsole = false;

        /**
         * 设置ffmpeg可执行文件的路径
         * @param ffmpeg 指向ffmpeg可执行程序的完整路径
         */
        public Builder setFfmpeg(Path ffmpeg) {
            this.ffmpeg = ffmpeg;
            return this;
        }

        /**
         * 设置格式转换的临时目录，若目录不存在会尝试创建
         * @param tempDirectory 存放转换时生成的临时文件的完成目录路径
         */
        public Builder setTempDirectory(Path tempDirectory) {
            this.tempDirectory = tempDirectory;
            return this;
        }

        /**
         * 设置最终输出的目录，如目录不存在会尝试创建
         * @param outDirectory 存放转换最终输出的文件的完整目录路径
         */
        public Builder setOutDirectory(Path outDirectory) {
            this.outDirectory = outDirectory;
            return this;
        }

        /**
         * 设置是否在控制台输出日志
         * @param enableOutputConsole 默认为false
         */
        public Builder setEnableOutputConsole(boolean enableOutputConsole) {
            this.enableOutputConsole = enableOutputConsole;
            return this;
        }

        public FfmpegMerger build() throws FileSystemException {
            check();
            return new FfmpegMerger(this);
        }

        private void check() throws FileSystemException {
            if(ffmpeg == null){
                throw new IllegalArgumentException("ffmpeg is null");
            }
            if(tempDirectory == null){
                throw new IllegalArgumentException("tempDirectory is null");
            }
            if(outDirectory == null){
                throw new IllegalArgumentException("outDirectory is null");
            }
            if(!ffmpeg.toFile().exists()){
                throw new IllegalArgumentException("ffmpeg does not exist: " + ffmpeg);
            }
            File tempDir = tempDirectory.toFile();
            if(!tempDir.exists()){
                if(!tempDir.mkdirs()){
                    throw new FileSystemException("cannot create dir: " + tempDir);
                }
            }
            File outDir = outDirectory.toFile();
            if(!outDir.exists()){
                if(!outDir.mkdirs()){
                    throw new FileSystemException("cannot create dir: " + outDir);
                }
            }
        }
    }
}
